{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qN8P0AnTnAhh"
      },
      "source": [
        "##### Copyright 2021 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "p8SrVqkmnDQv"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AftvNA5VMemJ"
      },
      "source": [
        "# Federated Reconstruction for Matrix Factorization"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "coAumH42q9nz"
      },
      "source": [
        "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://www.tensorflow.org/federated/tutorials/federated_reconstruction_for_matrix_factorization\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/federated/blob/v0.84.0/docs/tutorials/federated_reconstruction_for_matrix_factorization.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca target=\"_blank\" href=\"https://github.com/tensorflow/federated/blob/v0.84.0/docs/tutorials/federated_reconstruction_for_matrix_factorization.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on GitHub\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "  \u003ctd\u003e\n",
        "    \u003ca href=\"https://storage.googleapis.com/tensorflow_docs/federated/docs/tutorials/federated_reconstruction_for_matrix_factorization.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/download_logo_32px.png\" /\u003eDownload notebook\u003c/a\u003e\n",
        "  \u003c/td\u003e\n",
        "\u003c/table\u003e"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mxV9o4VmWNti"
      },
      "source": [
        "This tutorial explores *partially local federated learning*, where some client parameters are never aggregated on the server. This is useful for models with user-specific parameters (e.g. matrix factorization models) and for training in communication-limited settings. We build on concepts introduced in the [Federated Learning for Image Classification](https://www.tensorflow.org/federated/tutorials/federated_learning_for_image_classification) tutorial; as in that tutorial, we introduce high-level APIs in `tff.learning` for federated training and evaluation.\n",
        "\n",
        "We begin by motivating partially local federated learning for [matrix factorization](https://en.wikipedia.org/wiki/Matrix_factorization_(recommender_systems)). We describe Federated Reconstruction ([paper](https://arxiv.org/abs/2102.03448), [blog post](https://ai.googleblog.com/2021/12/a-scalable-approach-for-partially-local.html)), a practical algorithm for partially local federated learning at scale. We prepare the MovieLens 1M dataset, build a partially local model, and train and evaluate it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "I8pu6-dckG_u"
      },
      "outputs": [],
      "source": [
        "#@test {\"skip\": true}\n",
        "!pip install --quiet --upgrade tensorflow-federated\n",
        "!pip install --quiet --upgrade nest-asyncio\n",
        "\n",
        "import nest_asyncio\n",
        "nest_asyncio.apply()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "2txfde-th95B"
      },
      "outputs": [],
      "source": [
        "import collections\n",
        "import functools\n",
        "import io\n",
        "import os\n",
        "import requests\n",
        "import zipfile\n",
        "from typing import List, Optional, Tuple\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import tensorflow as tf\n",
        "import tensorflow_federated as tff\n",
        "\n",
        "np.random.seed(42)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "229PrhyXaw_Y"
      },
      "source": [
        "## Background: Matrix Factorization\n",
        "\n",
        "[Matrix factorization](https://en.wikipedia.org/wiki/Matrix_factorization_(recommender_systems)) has been a historically popular technique for learning recommendations and embedding representations for items based on user interactions. The canonical example is movie recommendation, where there are $n$ users and $m$ movies, and users have rated some movies. Given a user, we use their rating history and the ratings of similar users to predict the user's ratings for movies they haven't seen. If we have a model that can predict ratings, it's easy to recommend users new movies that they'll enjoy.\n",
        "\n",
        "For this task, it's useful to represent users' ratings as an $n \\times m$ matrix $R$:\n",
        "\n",
        "![Matrix Factorization Motivation (CC BY-SA 3.0; Wikipedia User Moshanin)](https://upload.wikimedia.org/wikipedia/commons/5/52/Collaborative_filtering.gif)\n",
        "\n",
        "This matrix is generally sparse, since users typically only see a small fraction of the movies in the dataset. The output of matrix factorization is two matrices: an $n \\times k$ matrix $U$ representing $k$-dimensional user embeddings for each user, and an $m \\times k$ matrix $I$ representing $k$-dimensional item embeddings for each item. The simplest training objective is to ensure that the dot product of user and item embeddings are predictive of observed ratings $O$:\n",
        "\n",
        "$$argmin_{U,I}  \\sum_{(u, i) \\in O} (R_{ui} - U_u I_i^T)^2$$\n",
        "\n",
        "This is equivalent to minimizing the mean squared error between observed ratings and ratings predicted by taking the dot product of the corresponding user and item embeddings. Another way to interpret this is that this ensures that $R \\approx UI^T$ for known ratings, hence \"matrix factorization\". If this is confusing, don't worry–we won't need to know the details of matrix factorization for the rest of the tutorial."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7O37nOQRvAjw"
      },
      "source": [
        "## Exploring MovieLens Data\n",
        "\n",
        "Let's start by loading the [MovieLens 1M](https://grouplens.org/datasets/movielens/1m/) data, which consists of 1,000,209 movie ratings from 6040 users on 3706 movies."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "id": "DwxoBLaWneOE"
      },
      "outputs": [],
      "source": [
        "def download_movielens_data(dataset_path):\n",
        "  \"\"\"Downloads and copies MovieLens data to local /tmp directory.\"\"\"\n",
        "  if dataset_path.startswith('http'):\n",
        "    r = requests.get(dataset_path)\n",
        "    z = zipfile.ZipFile(io.BytesIO(r.content))\n",
        "    z.extractall(path='/tmp')\n",
        "  else:\n",
        "    tf.io.gfile.makedirs('/tmp/ml-1m/')\n",
        "    for filename in ['ratings.dat', 'movies.dat', 'users.dat']:\n",
        "      tf.io.gfile.copy(\n",
        "          os.path.join(dataset_path, filename),\n",
        "          os.path.join('/tmp/ml-1m/', filename),\n",
        "          overwrite=True)\n",
        "\n",
        "download_movielens_data('http://files.grouplens.org/datasets/movielens/ml-1m.zip')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "id": "Y6_bskRUniqB"
      },
      "outputs": [],
      "source": [
        "def load_movielens_data(\n",
        "    data_directory: str = \"/tmp\",\n",
        ") -\u003e Tuple[pd.DataFrame, pd.DataFrame]:\n",
        "  \"\"\"Loads pandas DataFrames for ratings, movies, users from data directory.\"\"\"\n",
        "  # Load pandas DataFrames from data directory. Assuming data is formatted as\n",
        "  # specified in http://files.grouplens.org/datasets/movielens/ml-1m-README.txt.\n",
        "  ratings_df = pd.read_csv(\n",
        "      os.path.join(data_directory, \"ml-1m\", \"ratings.dat\"),\n",
        "      sep=\"::\",\n",
        "      names=[\"UserID\", \"MovieID\", \"Rating\", \"Timestamp\"], engine=\"python\")\n",
        "  movies_df = pd.read_csv(\n",
        "      os.path.join(data_directory, \"ml-1m\", \"movies.dat\"),\n",
        "      sep=\"::\",\n",
        "      names=[\"MovieID\", \"Title\", \"Genres\"], engine=\"python\", \n",
        "      encoding = \"ISO-8859-1\")\n",
        "\n",
        "  # Create dictionaries mapping from old IDs to new (remapped) IDs for both\n",
        "  # MovieID and UserID. Use the movies and users present in ratings_df to\n",
        "  # determine the mapping, since movies and users without ratings are unneeded.\n",
        "  movie_mapping = {\n",
        "      old_movie: new_movie for new_movie, old_movie in enumerate(\n",
        "          ratings_df.MovieID.astype(\"category\").cat.categories)\n",
        "  }\n",
        "  user_mapping = {\n",
        "      old_user: new_user for new_user, old_user in enumerate(\n",
        "          ratings_df.UserID.astype(\"category\").cat.categories)\n",
        "  }\n",
        "\n",
        "  # Map each DataFrame consistently using the now-fixed mapping.\n",
        "  ratings_df.MovieID = ratings_df.MovieID.map(movie_mapping)\n",
        "  ratings_df.UserID = ratings_df.UserID.map(user_mapping)\n",
        "  movies_df.MovieID = movies_df.MovieID.map(movie_mapping)\n",
        "\n",
        "  # Remove nulls resulting from some movies being in movies_df but not\n",
        "  # ratings_df.\n",
        "  movies_df = movies_df[pd.notnull(movies_df.MovieID)]\n",
        "\n",
        "  return ratings_df, movies_df"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nqVrh1o9t1cZ"
      },
      "source": [
        "Let's load and explore a couple Pandas DataFrames containing the rating and movie data."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "id": "OkAh5nt_n4ll"
      },
      "outputs": [],
      "source": [
        "ratings_df, movies_df = load_movielens_data()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6aNtIwvNuP7v"
      },
      "source": [
        "We can see that each rating example has a rating from 1-5, a corresponding UserID, a corresponding MovieID, and a timestamp."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "id": "G4qap4n-C83I"
      },
      "outputs": [
        {
          "data": {
            "text/html": [
              "\n",
              "  \u003cdiv id=\"df-0cbb8917-9b5c-4d30-afd5-c7310447c6d5\"\u003e\n",
              "    \u003cdiv class=\"colab-df-container\"\u003e\n",
              "      \u003cdiv\u003e\n",
              "\u003cstyle scoped\u003e\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "\u003c/style\u003e\n",
              "\u003ctable border=\"1\" class=\"dataframe\"\u003e\n",
              "  \u003cthead\u003e\n",
              "    \u003ctr style=\"text-align: right;\"\u003e\n",
              "      \u003cth\u003e\u003c/th\u003e\n",
              "      \u003cth\u003eUserID\u003c/th\u003e\n",
              "      \u003cth\u003eMovieID\u003c/th\u003e\n",
              "      \u003cth\u003eRating\u003c/th\u003e\n",
              "      \u003cth\u003eTimestamp\u003c/th\u003e\n",
              "    \u003c/tr\u003e\n",
              "  \u003c/thead\u003e\n",
              "  \u003ctbody\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e0\u003c/th\u003e\n",
              "      \u003ctd\u003e0\u003c/td\u003e\n",
              "      \u003ctd\u003e1104\u003c/td\u003e\n",
              "      \u003ctd\u003e5\u003c/td\u003e\n",
              "      \u003ctd\u003e978300760\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e1\u003c/th\u003e\n",
              "      \u003ctd\u003e0\u003c/td\u003e\n",
              "      \u003ctd\u003e639\u003c/td\u003e\n",
              "      \u003ctd\u003e3\u003c/td\u003e\n",
              "      \u003ctd\u003e978302109\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e2\u003c/th\u003e\n",
              "      \u003ctd\u003e0\u003c/td\u003e\n",
              "      \u003ctd\u003e853\u003c/td\u003e\n",
              "      \u003ctd\u003e3\u003c/td\u003e\n",
              "      \u003ctd\u003e978301968\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e3\u003c/th\u003e\n",
              "      \u003ctd\u003e0\u003c/td\u003e\n",
              "      \u003ctd\u003e3177\u003c/td\u003e\n",
              "      \u003ctd\u003e4\u003c/td\u003e\n",
              "      \u003ctd\u003e978300275\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e4\u003c/th\u003e\n",
              "      \u003ctd\u003e0\u003c/td\u003e\n",
              "      \u003ctd\u003e2162\u003c/td\u003e\n",
              "      \u003ctd\u003e5\u003c/td\u003e\n",
              "      \u003ctd\u003e978824291\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "  \u003c/tbody\u003e\n",
              "\u003c/table\u003e\n",
              "\u003c/div\u003e\n",
              "      \u003cbutton class=\"colab-df-convert\" onclick=\"convertToInteractive('df-0cbb8917-9b5c-4d30-afd5-c7310447c6d5')\"\n",
              "              title=\"Convert this dataframe to an interactive table.\"\n",
              "              style=\"display:none;\"\u003e\n",
              "        \n",
              "  \u003csvg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "       width=\"24px\"\u003e\n",
              "    \u003cpath d=\"M0 0h24v24H0V0z\" fill=\"none\"/\u003e\n",
              "    \u003cpath d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/\u003e\u003cpath d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/\u003e\n",
              "  \u003c/svg\u003e\n",
              "      \u003c/button\u003e\n",
              "      \n",
              "  \n",
              "    \u003cdiv id=\"df-47374e1b-52da-49ae-a0e3-eaabc05a7671\"\u003e\n",
              "      \u003cbutton class=\"colab-df-quickchart\" onclick=\"quickchart('df-47374e1b-52da-49ae-a0e3-eaabc05a7671')\"\n",
              "              title=\"Generate charts.\"\n",
              "              style=\"display:none;\"\u003e\n",
              "        \n",
              "  \u003csvg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "       width=\"24px\"\u003e\n",
              "      \u003cg\u003e\n",
              "          \u003cpath d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/\u003e\n",
              "      \u003c/g\u003e\n",
              "  \u003c/svg\u003e\n",
              "      \u003c/button\u003e\n",
              "    \u003c/div\u003e\n",
              "    \n",
              "  \u003cstyle\u003e\n",
              "    .colab-df-quickchart {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-quickchart:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-quickchart {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-quickchart:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "\n",
              "    .colab-quickchart-section-title {\n",
              "        clear: both;\n",
              "    }\n",
              "  \u003c/style\u003e\n",
              "\n",
              "    \u003cscript\u003e\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#df-47374e1b-52da-49ae-a0e3-eaabc05a7671 button.colab-df-quickchart');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function quickchart(key) {\n",
              "        const containerElement = document.querySelector('#df-47374e1b-52da-49ae-a0e3-eaabc05a7671');\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'generateCharts', [key], {});\n",
              "      }\n",
              "    \u003c/script\u003e\n",
              "  \u003cstyle\u003e\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      flex-wrap:wrap;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  \u003c/style\u003e\n",
              "\n",
              "      \u003cscript\u003e\n",
              "        const buttonEl =\n",
              "          document.querySelector('#df-0cbb8917-9b5c-4d30-afd5-c7310447c6d5 button.colab-df-convert');\n",
              "        buttonEl.style.display =\n",
              "          google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "        async function convertToInteractive(key) {\n",
              "          const element = document.querySelector('#df-0cbb8917-9b5c-4d30-afd5-c7310447c6d5');\n",
              "          const dataTable =\n",
              "            await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                     [key], {});\n",
              "          if (!dataTable) return;\n",
              "\n",
              "          const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "            '\u003ca target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb\u003edata table notebook\u003c/a\u003e'\n",
              "            + ' to learn more about interactive tables.';\n",
              "          element.innerHTML = '';\n",
              "          dataTable['output_type'] = 'display_data';\n",
              "          await google.colab.output.renderOutput(dataTable, element);\n",
              "          const docLink = document.createElement('div');\n",
              "          docLink.innerHTML = docLinkHtml;\n",
              "          element.appendChild(docLink);\n",
              "        }\n",
              "      \u003c/script\u003e\n",
              "    \u003c/div\u003e\n",
              "  \u003c/div\u003e\n",
              "  "
            ],
            "text/plain": [
              "   UserID  MovieID  Rating  Timestamp\n",
              "0       0     1104       5  978300760\n",
              "1       0      639       3  978302109\n",
              "2       0      853       3  978301968\n",
              "3       0     3177       4  978300275\n",
              "4       0     2162       5  978824291"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "ratings_df.head()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5og9HO-ZubIJ"
      },
      "source": [
        "Each movie has a title and potentially multiple genres."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "id": "5TyN-30NC91Z"
      },
      "outputs": [
        {
          "data": {
            "text/html": [
              "\n",
              "  \u003cdiv id=\"df-256412c5-ca12-40a8-87c3-5d09ba4b2301\"\u003e\n",
              "    \u003cdiv class=\"colab-df-container\"\u003e\n",
              "      \u003cdiv\u003e\n",
              "\u003cstyle scoped\u003e\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "\u003c/style\u003e\n",
              "\u003ctable border=\"1\" class=\"dataframe\"\u003e\n",
              "  \u003cthead\u003e\n",
              "    \u003ctr style=\"text-align: right;\"\u003e\n",
              "      \u003cth\u003e\u003c/th\u003e\n",
              "      \u003cth\u003eMovieID\u003c/th\u003e\n",
              "      \u003cth\u003eTitle\u003c/th\u003e\n",
              "      \u003cth\u003eGenres\u003c/th\u003e\n",
              "    \u003c/tr\u003e\n",
              "  \u003c/thead\u003e\n",
              "  \u003ctbody\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e0\u003c/th\u003e\n",
              "      \u003ctd\u003e0.0\u003c/td\u003e\n",
              "      \u003ctd\u003eToy Story (1995)\u003c/td\u003e\n",
              "      \u003ctd\u003eAnimation|Children's|Comedy\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e1\u003c/th\u003e\n",
              "      \u003ctd\u003e1.0\u003c/td\u003e\n",
              "      \u003ctd\u003eJumanji (1995)\u003c/td\u003e\n",
              "      \u003ctd\u003eAdventure|Children's|Fantasy\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e2\u003c/th\u003e\n",
              "      \u003ctd\u003e2.0\u003c/td\u003e\n",
              "      \u003ctd\u003eGrumpier Old Men (1995)\u003c/td\u003e\n",
              "      \u003ctd\u003eComedy|Romance\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e3\u003c/th\u003e\n",
              "      \u003ctd\u003e3.0\u003c/td\u003e\n",
              "      \u003ctd\u003eWaiting to Exhale (1995)\u003c/td\u003e\n",
              "      \u003ctd\u003eComedy|Drama\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "    \u003ctr\u003e\n",
              "      \u003cth\u003e4\u003c/th\u003e\n",
              "      \u003ctd\u003e4.0\u003c/td\u003e\n",
              "      \u003ctd\u003eFather of the Bride Part II (1995)\u003c/td\u003e\n",
              "      \u003ctd\u003eComedy\u003c/td\u003e\n",
              "    \u003c/tr\u003e\n",
              "  \u003c/tbody\u003e\n",
              "\u003c/table\u003e\n",
              "\u003c/div\u003e\n",
              "      \u003cbutton class=\"colab-df-convert\" onclick=\"convertToInteractive('df-256412c5-ca12-40a8-87c3-5d09ba4b2301')\"\n",
              "              title=\"Convert this dataframe to an interactive table.\"\n",
              "              style=\"display:none;\"\u003e\n",
              "        \n",
              "  \u003csvg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "       width=\"24px\"\u003e\n",
              "    \u003cpath d=\"M0 0h24v24H0V0z\" fill=\"none\"/\u003e\n",
              "    \u003cpath d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/\u003e\u003cpath d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/\u003e\n",
              "  \u003c/svg\u003e\n",
              "      \u003c/button\u003e\n",
              "      \n",
              "  \n",
              "    \u003cdiv id=\"df-4e0745aa-6f4e-4e38-b1f8-a1484569d13d\"\u003e\n",
              "      \u003cbutton class=\"colab-df-quickchart\" onclick=\"quickchart('df-4e0745aa-6f4e-4e38-b1f8-a1484569d13d')\"\n",
              "              title=\"Generate charts.\"\n",
              "              style=\"display:none;\"\u003e\n",
              "        \n",
              "  \u003csvg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "       width=\"24px\"\u003e\n",
              "      \u003cg\u003e\n",
              "          \u003cpath d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/\u003e\n",
              "      \u003c/g\u003e\n",
              "  \u003c/svg\u003e\n",
              "      \u003c/button\u003e\n",
              "    \u003c/div\u003e\n",
              "    \n",
              "  \u003cstyle\u003e\n",
              "    .colab-df-quickchart {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-quickchart:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-quickchart {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-quickchart:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "\n",
              "    .colab-quickchart-section-title {\n",
              "        clear: both;\n",
              "    }\n",
              "  \u003c/style\u003e\n",
              "\n",
              "    \u003cscript\u003e\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#df-4e0745aa-6f4e-4e38-b1f8-a1484569d13d button.colab-df-quickchart');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function quickchart(key) {\n",
              "        const containerElement = document.querySelector('#df-4e0745aa-6f4e-4e38-b1f8-a1484569d13d');\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'generateCharts', [key], {});\n",
              "      }\n",
              "    \u003c/script\u003e\n",
              "  \u003cstyle\u003e\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      flex-wrap:wrap;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  \u003c/style\u003e\n",
              "\n",
              "      \u003cscript\u003e\n",
              "        const buttonEl =\n",
              "          document.querySelector('#df-256412c5-ca12-40a8-87c3-5d09ba4b2301 button.colab-df-convert');\n",
              "        buttonEl.style.display =\n",
              "          google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "        async function convertToInteractive(key) {\n",
              "          const element = document.querySelector('#df-256412c5-ca12-40a8-87c3-5d09ba4b2301');\n",
              "          const dataTable =\n",
              "            await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                     [key], {});\n",
              "          if (!dataTable) return;\n",
              "\n",
              "          const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "            '\u003ca target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb\u003edata table notebook\u003c/a\u003e'\n",
              "            + ' to learn more about interactive tables.';\n",
              "          element.innerHTML = '';\n",
              "          dataTable['output_type'] = 'display_data';\n",
              "          await google.colab.output.renderOutput(dataTable, element);\n",
              "          const docLink = document.createElement('div');\n",
              "          docLink.innerHTML = docLinkHtml;\n",
              "          element.appendChild(docLink);\n",
              "        }\n",
              "      \u003c/script\u003e\n",
              "    \u003c/div\u003e\n",
              "  \u003c/div\u003e\n",
              "  "
            ],
            "text/plain": [
              "   MovieID                               Title                        Genres\n",
              "0      0.0                    Toy Story (1995)   Animation|Children's|Comedy\n",
              "1      1.0                      Jumanji (1995)  Adventure|Children's|Fantasy\n",
              "2      2.0             Grumpier Old Men (1995)                Comedy|Romance\n",
              "3      3.0            Waiting to Exhale (1995)                  Comedy|Drama\n",
              "4      4.0  Father of the Bride Part II (1995)                        Comedy"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "movies_df.head()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YWsip1k5ue5B"
      },
      "source": [
        "It's always a good idea to understand basic statistics of the dataset:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "id": "8I1jgmDOCqt4"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Num users: 6040\n",
            "Num movies: 3706\n"
          ]
        }
      ],
      "source": [
        "print('Num users:', len(set(ratings_df.UserID)))\n",
        "print('Num movies:', len(set(ratings_df.MovieID)))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {
        "id": "1aO07Lg21Joa"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\nAABJRU5ErkJggg==\n",
            "text/plain": [
              "\u003cFigure size 600x400 with 1 Axes\u003e"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Average rating: 3.581564453029317\n",
            "Median rating: 4.0\n"
          ]
        }
      ],
      "source": [
        "ratings = ratings_df.Rating.tolist()\n",
        "\n",
        "plt.hist(ratings, bins=5)\n",
        "plt.xticks([1, 2, 3, 4, 5])\n",
        "plt.ylabel('Count')\n",
        "plt.xlabel('Rating')\n",
        "plt.show()\n",
        "\n",
        "print('Average rating:', np.mean(ratings))\n",
        "print('Median rating:', np.median(ratings))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "poMbHDQguqPA"
      },
      "source": [
        "We can also plot the most popular movie genres."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "id": "1gYdfRoOw04z"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\nQmCC\n",
            "text/plain": [
              "\u003cFigure size 1100x1100 with 1 Axes\u003e"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "movie_genres_list = movies_df.Genres.tolist()\n",
        "# Count the number of times each genre describes a movie.\n",
        "genre_count = collections.defaultdict(int)\n",
        "for genres in movie_genres_list:\n",
        "  curr_genres_list = genres.split('|')\n",
        "  for genre in curr_genres_list:\n",
        "    genre_count[genre] += 1\n",
        "genre_name_list, genre_count_list = zip(*genre_count.items())\n",
        "\n",
        "plt.figure(figsize=(11, 11))\n",
        "plt.pie(genre_count_list, labels=genre_name_list)\n",
        "plt.title('MovieLens Movie Genres')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "evWb8hg8vk-P"
      },
      "source": [
        "This data is naturally partitioned into ratings from different users, so we'd expect some heterogeneity in data between clients. Below we display the most commonly rated movie genres for different users. We can observe significant differences between users."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "id": "EfAeZ7f0GlSo"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "For user 0:\n",
            "Drama was rated 21 times\n",
            "Children's was rated 20 times\n",
            "Animation was rated 18 times\n",
            "Musical was rated 14 times\n",
            "Comedy was rated 14 times\n",
            "\n",
            "For user 10:\n",
            "Comedy was rated 84 times\n",
            "Drama was rated 54 times\n",
            "Romance was rated 22 times\n",
            "Thriller was rated 18 times\n",
            "Action was rated 9 times\n",
            "\n",
            "For user 19:\n",
            "Action was rated 17 times\n",
            "Sci-Fi was rated 9 times\n",
            "Thriller was rated 9 times\n",
            "Drama was rated 6 times\n",
            "Crime was rated 5 times\n"
          ]
        }
      ],
      "source": [
        "def print_top_genres_for_user(ratings_df, movies_df, user_id):\n",
        "  \"\"\"Prints top movie genres for user with ID user_id.\"\"\"\n",
        "  user_ratings_df = ratings_df[ratings_df.UserID == user_id]\n",
        "  movie_ids = user_ratings_df.MovieID\n",
        "\n",
        "  genre_count = collections.Counter()\n",
        "  for movie_id in movie_ids:\n",
        "    genres_string = movies_df[movies_df.MovieID == movie_id].Genres.tolist()[0]\n",
        "    for genre in genres_string.split('|'):\n",
        "      genre_count[genre] += 1\n",
        "\n",
        "  print(f'\\nFor user {user_id}:')\n",
        "  for (genre, freq) in genre_count.most_common(5):\n",
        "    print(f'{genre} was rated {freq} times')\n",
        "\n",
        "print_top_genres_for_user(ratings_df, movies_df, user_id=0)\n",
        "print_top_genres_for_user(ratings_df, movies_df, user_id=10)\n",
        "print_top_genres_for_user(ratings_df, movies_df, user_id=19)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-p88NsfPwTOP"
      },
      "source": [
        "## Preprocessing MovieLens Data\n",
        "\n",
        "We'll now prepare the MovieLens dataset as a list of `tf.data.Dataset`s representing each user's data for use with TFF.\n",
        "\n",
        "We implement two functions:\n",
        "* `create_tf_datasets`: takes our ratings DataFrame and produces a list of user-split `tf.data.Dataset`s.\n",
        "* `split_tf_datasets`: takes a list of datasets and splits them into train/val/test by *user*, so the val/test sets contain only ratings from users **unseen** during training. Typically in standard centralized matrix factorization we actually split so that the val/test sets contain held-out ratings from **seen** users, since unseen users don't have user embeddings. In our case, we'll see later that the approach we use to enable matrix factorization in FL also enables quickly reconstructing user embeddings for unseen users."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {
        "id": "DHwb2AsvtIwO"
      },
      "outputs": [],
      "source": [
        "def create_tf_datasets(ratings_df: pd.DataFrame,\n",
        "                       batch_size: int = 1,\n",
        "                       max_examples_per_user: Optional[int] = None,\n",
        "                       max_clients: Optional[int] = None) -\u003e List[tf.data.Dataset]:\n",
        "  \"\"\"Creates TF Datasets containing the movies and ratings for all users.\"\"\"\n",
        "  num_users = len(set(ratings_df.UserID))\n",
        "  # Optionally limit to `max_clients` to speed up data loading.\n",
        "  if max_clients is not None:\n",
        "    num_users = min(num_users, max_clients)\n",
        "\n",
        "  def rating_batch_map_fn(rating_batch):\n",
        "    \"\"\"Maps a rating batch to an OrderedDict with tensor values.\"\"\"\n",
        "    # Each example looks like: {x: movie_id, y: rating}.\n",
        "    # We won't need the UserID since each client will only look at their own\n",
        "    # data.\n",
        "    return collections.OrderedDict([\n",
        "        (\"x\", tf.cast(rating_batch[:, 1:2], tf.int64)),\n",
        "        (\"y\", tf.cast(rating_batch[:, 2:3], tf.float32))\n",
        "    ])\n",
        "\n",
        "  tf_datasets = []\n",
        "  for user_id in range(num_users):\n",
        "    # Get subset of ratings_df belonging to a particular user.\n",
        "    user_ratings_df = ratings_df[ratings_df.UserID == user_id]\n",
        "\n",
        "    tf_dataset = tf.data.Dataset.from_tensor_slices(user_ratings_df)\n",
        "\n",
        "    # Define preprocessing operations.\n",
        "    tf_dataset = tf_dataset.take(max_examples_per_user).shuffle(\n",
        "        buffer_size=max_examples_per_user, seed=42).batch(batch_size).map(\n",
        "        rating_batch_map_fn,\n",
        "        num_parallel_calls=tf.data.experimental.AUTOTUNE)\n",
        "    tf_datasets.append(tf_dataset)\n",
        "\n",
        "  return tf_datasets\n",
        "\n",
        "\n",
        "def split_tf_datasets(\n",
        "    tf_datasets: List[tf.data.Dataset],\n",
        "    train_fraction: float = 0.8,\n",
        "    val_fraction: float = 0.1,\n",
        ") -\u003e Tuple[List[tf.data.Dataset], List[tf.data.Dataset], List[tf.data.Dataset]]:\n",
        "  \"\"\"Splits a list of user TF datasets into train/val/test by user.\n",
        "  \"\"\"\n",
        "  np.random.seed(42)\n",
        "  np.random.shuffle(tf_datasets)\n",
        "\n",
        "  train_idx = int(len(tf_datasets) * train_fraction)\n",
        "  val_idx = int(len(tf_datasets) * (train_fraction + val_fraction))\n",
        "\n",
        "  # Note that the val and test data contains completely different users, not\n",
        "  # just unseen ratings from train users.\n",
        "  return (tf_datasets[:train_idx], tf_datasets[train_idx:val_idx],\n",
        "          tf_datasets[val_idx:])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "id": "T6pJVpHfns9q"
      },
      "outputs": [],
      "source": [
        "# We limit the number of clients to speed up dataset creation. Feel free to pass\n",
        "# max_clients=None to load all clients' data.\n",
        "tf_datasets = create_tf_datasets(\n",
        "    ratings_df=ratings_df,\n",
        "    batch_size=5,\n",
        "    max_examples_per_user=300,\n",
        "    max_clients=2000)\n",
        "\n",
        "# Split the ratings into training/val/test by client.\n",
        "tf_train_datasets, tf_val_datasets, tf_test_datasets = split_tf_datasets(\n",
        "    tf_datasets,\n",
        "    train_fraction=0.8,\n",
        "    val_fraction=0.1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "T2SdGARZ0-cm"
      },
      "source": [
        "As a quick check, we can print a batch of training data. We can see that each individual example contains a MovieID under the \"x\" key and a rating under the \"y\" key. Note that we won't need the UserID since each user only sees their own data."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "id": "9D2rCgcwFP4E"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "OrderedDict([('x', \u003ctf.Tensor: shape=(5, 1), dtype=int64, numpy=\n",
            "array([[1907],\n",
            "       [2891],\n",
            "       [1574],\n",
            "       [2785],\n",
            "       [2775]])\u003e), ('y', \u003ctf.Tensor: shape=(5, 1), dtype=float32, numpy=\n",
            "array([[3.],\n",
            "       [3.],\n",
            "       [3.],\n",
            "       [4.],\n",
            "       [3.]], dtype=float32)\u003e)])\n"
          ]
        }
      ],
      "source": [
        "print(next(iter(tf_train_datasets[0])))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VOaSLuFK18G7"
      },
      "source": [
        "We can plot a histogram showing the number of ratings per user."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "metadata": {
        "id": "98VwSFBe1GPM"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "\u003cFigure size 600x400 with 1 Axes\u003e"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def count_examples(curr_count, batch):\n",
        "  return curr_count + tf.size(batch['x'])\n",
        "\n",
        "num_examples_list = []\n",
        "# Compute number of examples for every other user.\n",
        "for i in range(0, len(tf_train_datasets), 2):\n",
        "  num_examples = tf_train_datasets[i].reduce(tf.constant(0), count_examples).numpy()\n",
        "  num_examples_list.append(num_examples)\n",
        "\n",
        "plt.hist(num_examples_list, bins=10)\n",
        "plt.ylabel('Count')\n",
        "plt.xlabel('Number of Examples')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eqz6oRm22FWM"
      },
      "source": [
        "Now that we've loaded and explored the data, we'll discuss how to bring matrix factorization to federated learning. Along the way, we'll motivate partially local federated learning."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PMZLj5WprMJM"
      },
      "source": [
        "## Bringing Matrix Factorization to FL\n",
        "\n",
        "While matrix factorization has been traditionally used in centralized settings, it's especially relevant in federated learning:  user ratings may live on separate client devices, and we may want to learn embeddings and recommendations for users and items without centralizing the data. Since each user has a corresponding user embedding, it's natural to have each client store their user embedding–this scales much better than a central server storing all the user embeddings.\n",
        "\n",
        "One proposal for bringing matrix factorization to FL goes as follows:\n",
        "1. The server stores and sends the item matrix $I$ to sampled clients each round\n",
        "2. Clients update the item matrix and their personal user embedding $U_u$ using SGD on the above objective\n",
        "3. Updates to $I$ are aggregated on the server, updating the server copy of $I$ for the next round\n",
        "\n",
        "This approach is *partially local*–that is, some client parameters are never aggregated by the server. Though this approach is appealing, it requires clients to maintain state across rounds, namely their user embeddings. Stateful federated algorithms are less appropriate for cross-device FL settings: in these settings the population size is often much larger than the number of clients that participate in each round, and a client usually participates at most once during the training process. Besides relying on state that may not be initialized, stateful algorithms can result in performance degradation in cross-device settings due to state getting *stale* when clients are infrequently sampled. Importantly, in the matrix factorization setting, a stateful algorithm leads to all unseen clients missing trained user embeddings, and in large-scale training the majority of users may be unseen. For more on the motivation for stateless algorithms in cross-device FL, see [Wang et al. 2021 Sec. 3.1.1](https://arxiv.org/pdf/2107.06917.pdf) and [Reddi et al. 2020 Sec. 5.1](https://arxiv.org/abs/2003.00295).\n",
        "\n",
        "Federated Reconstruction ([Singhal et al. 2021](https://arxiv.org/abs/2102.03448)) is a stateless alternative to the aforementioned approach. The key idea is that instead of storing user embeddings across rounds, clients reconstruct user embeddings when needed. When FedRecon is applied to matrix factorization, training proceeds as follows:\n",
        "1. The server stores and sends the item matrix $I$ to sampled clients each round\n",
        "2. Each client freezes $I$ and trains their user embedding $U_u$ using one or more steps of SGD (reconstruction)\n",
        "3. Each client freezes $U_u$ and trains $I$ using one or more steps of SGD\n",
        "4. Updates to $I$ are aggregated across users, updating the server copy of $I$ for the next round\n",
        "\n",
        "This approach does not require clients to maintain state across rounds. The authors also show in the paper that this method leads to fast reconstruction of user embeddings for unseen clients (Sec. 4.2, Fig. 3, and Table 1), allowing the majority of clients who do not participate in training to have a trained model, enabling recommendations for these clients. See the Federated Reconstruction [Google AI Blog post](https://ai.googleblog.com/2021/12/a-scalable-approach-for-partially-local.html) for more key results."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "imwLf1zksCjN"
      },
      "source": [
        "## Defining the Model\n",
        "\n",
        "We'll next define the local matrix factorization model to be trained on client devices. This model will include the full item matrix $I$ and a single user embedding $U_u$ for client $u$. Note that clients will not need to store the full user matrix $U$.\n",
        "\n",
        "We'll define the following:\n",
        "- `UserEmbedding`: a simple Keras layer representing a single `num_latent_factors`-dimensional user embedding.\n",
        "- `get_matrix_factorization_model`: a function that returns a [`tff.learning.models.ReconstructionModel`](https://www.tensorflow.org/federated/api_docs/python/tff/learning/models/ReconstructionModel) containing the model logic, including which layers are globally aggregated on the server and which layers remain local. We need this additional information to initialize the Federated Reconstruction training process. Here we produce the `tff.learning.models.ReconstructionModel` from a Keras model using [`tff.learning.models.ReconstructionModel.from_keras_model_and_layers`](https://www.tensorflow.org/federated/api_docs/python/tff/learning/models/ReconstructionModel#from_keras_model_and_layers). Similar to `tff.learning.models.VariableModel`, we can also implement a custom `tff.learning.models.ReconstructionModel` by implementing the class interface."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "metadata": {
        "id": "nSLMxPDP3D72"
      },
      "outputs": [],
      "source": [
        "class UserEmbedding(tf.keras.layers.Layer):\n",
        "  \"\"\"Keras layer representing an embedding for a single user, used below.\"\"\"\n",
        "\n",
        "  def __init__(self, num_latent_factors, **kwargs):\n",
        "    super().__init__(**kwargs)\n",
        "    self.num_latent_factors = num_latent_factors\n",
        "\n",
        "  def build(self, input_shape):\n",
        "    self.embedding = self.add_weight(\n",
        "        shape=(1, self.num_latent_factors),\n",
        "        initializer='uniform',\n",
        "        dtype=tf.float32,\n",
        "        name='UserEmbeddingKernel')\n",
        "    super().build(input_shape)\n",
        "\n",
        "  def call(self, inputs):\n",
        "    return self.embedding\n",
        "\n",
        "  def compute_output_shape(self):\n",
        "    return (1, self.num_latent_factors)\n",
        "\n",
        "\n",
        "def get_matrix_factorization_model(\n",
        "    num_items: int,\n",
        "    num_latent_factors: int) -\u003e tff.learning.models.ReconstructionModel:\n",
        "  \"\"\"Defines a Keras matrix factorization model.\"\"\"\n",
        "  # Layers with variables will be partitioned into global and local layers.\n",
        "  # We'll pass this to `tff.learning.models.ReconstructionModel.from_keras_model_and_layers`.\n",
        "  global_layers = []\n",
        "  local_layers = []\n",
        "\n",
        "  # Extract the item embedding.\n",
        "  item_input = tf.keras.layers.Input(shape=[1], name='Item')\n",
        "  item_embedding_layer = tf.keras.layers.Embedding(\n",
        "      num_items,\n",
        "      num_latent_factors,\n",
        "      name='ItemEmbedding')\n",
        "  global_layers.append(item_embedding_layer)\n",
        "  flat_item_vec = tf.keras.layers.Flatten(name='FlattenItems')(\n",
        "      item_embedding_layer(item_input))\n",
        "\n",
        "  # Extract the user embedding.\n",
        "  user_embedding_layer = UserEmbedding(\n",
        "      num_latent_factors,\n",
        "      name='UserEmbedding')\n",
        "  local_layers.append(user_embedding_layer)\n",
        "\n",
        "  # The item_input never gets used by the user embedding layer,\n",
        "  # but this allows the model to directly use the user embedding.\n",
        "  flat_user_vec = user_embedding_layer(item_input)\n",
        "\n",
        "  # Compute the dot product between the user embedding, and the item one.\n",
        "  pred = tf.keras.layers.Dot(\n",
        "      1, normalize=False, name='Dot')([flat_user_vec, flat_item_vec])\n",
        "\n",
        "  input_spec = collections.OrderedDict(\n",
        "      x=tf.TensorSpec(shape=[None, 1], dtype=tf.int64),\n",
        "      y=tf.TensorSpec(shape=[None, 1], dtype=tf.float32))\n",
        "\n",
        "  model = tf.keras.Model(inputs=item_input, outputs=pred)\n",
        "\n",
        "  return tff.learning.models.ReconstructionModel.from_keras_model_and_layers(\n",
        "      keras_model=model,\n",
        "      global_layers=global_layers,\n",
        "      local_layers=local_layers,\n",
        "      input_spec=input_spec)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-B3FPaRiwY3n"
      },
      "source": [
        "Analagous to the interface for Federated Averaging, the interface for Federated Reconstruction expects a `model_fn` with no arguments that returns a `tff.learning.models.ReconstructionModel`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "id": "vNBRQW9EwneZ"
      },
      "outputs": [],
      "source": [
        "# This will be used to produce our training process.\n",
        "# User and item embeddings will be 50-dimensional.\n",
        "model_fn = functools.partial(\n",
        "    get_matrix_factorization_model,\n",
        "    num_items=3706,\n",
        "    num_latent_factors=50)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fQVpVIfnwvPg"
      },
      "source": [
        "We'll next define `loss_fn` and `metrics_fn`, where `loss_fn` is a no-argument function returning a Keras loss to use to train the model, and `metrics_fn` is a no-argument function returning a list of Keras metrics for evaluation. These are needed to build the training and evaluation computations.\n",
        "\n",
        "We'll use Mean Squared Error as the loss, as mentioned above. For evaluation we'll use rating accuracy (when the model's predicted dot product is rounded to the nearest whole number, how often does it match the label rating?)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "metadata": {
        "id": "FDJUfeSNwxIL"
      },
      "outputs": [],
      "source": [
        "class RatingAccuracy(tf.keras.metrics.Mean):\n",
        "  \"\"\"Keras metric computing accuracy of reconstructed ratings.\"\"\"\n",
        "\n",
        "  def __init__(self,\n",
        "               name: str = 'rating_accuracy',\n",
        "               **kwargs):\n",
        "    super().__init__(name=name, **kwargs)\n",
        "\n",
        "  def update_state(self,\n",
        "                   y_true: tf.Tensor,\n",
        "                   y_pred: tf.Tensor,\n",
        "                   sample_weight: Optional[tf.Tensor] = None):\n",
        "    absolute_diffs = tf.abs(y_true - y_pred)\n",
        "    # A [batch_size, 1] tf.bool tensor indicating correctness within the\n",
        "    # threshold for each example in a batch. A 0.5 threshold corresponds\n",
        "    # to correctness when predictions are rounded to the nearest whole\n",
        "    # number.\n",
        "    example_accuracies = tf.less_equal(absolute_diffs, 0.5)\n",
        "    super().update_state(example_accuracies, sample_weight=sample_weight)\n",
        "\n",
        "\n",
        "loss_fn = lambda: tf.keras.losses.MeanSquaredError()\n",
        "metrics_fn = lambda: [RatingAccuracy()]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ecM_vru8xg2j"
      },
      "source": [
        "## Training and Evaluation\n",
        "\n",
        "Now we have everything we need to define the training process. One important difference from the [interface for Federated Averaging](https://www.tensorflow.org/federated/api_docs/python/tff/learning/algorithms/build_weighted_fed_avg) is that we now pass in a `reconstruction_optimizer_fn`, which will be used when reconstructing local parameters (in our case, user embeddings). It's generally reasonable to use `SGD` here, with a similar or slightly lower learning rate than the client optimizer learning rate. We provide a working configuration below. This hasn't been carefully tuned, so feel free to play around with different values.\n",
        "\n",
        "Check out the [documentation](https://www.tensorflow.org/federated/api_docs/python/tff/learning/reconstruction/build_training_process) for more details and options."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "metadata": {
        "id": "YQsX0FgtwsoE"
      },
      "outputs": [],
      "source": [
        "# We'll use this by doing:\n",
        "# state = training_process.initialize()\n",
        "# state, metrics = training_process.next(state, federated_train_data)\n",
        "training_process = tff.learning.algorithms.build_fed_recon(\n",
        "    model_fn=model_fn,\n",
        "    loss_fn=loss_fn,\n",
        "    metrics_fn=metrics_fn,\n",
        "    server_optimizer_fn=lambda: tf.keras.optimizers.SGD(1.0),\n",
        "    client_optimizer_fn=lambda: tf.keras.optimizers.SGD(0.5),\n",
        "    reconstruction_optimizer_fn=lambda: tf.keras.optimizers.SGD(0.1))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ssbbds4vzhXJ"
      },
      "source": [
        "We can also define a computation for evaluating our trained global model."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "metadata": {
        "id": "KHi7J330PtxO"
      },
      "outputs": [],
      "source": [
        "evaluation_process = tff.learning.algorithms.build_fed_recon_eval(\n",
        "    model_fn,\n",
        "    loss_fn=loss_fn,\n",
        "    metrics_fn=metrics_fn,\n",
        "    reconstruction_optimizer_fn=functools.partial(\n",
        "            tf.keras.optimizers.SGD, 0.1))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h_V_ZwlE0DSl"
      },
      "source": [
        "We can initialize the training process state and examine it. Most importantly, we can see that this server state only stores item variables (currently randomly initialized) and not any user embeddings."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "metadata": {
        "id": "I_kOjFVKQoNX"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "ModelWeights(trainable=[array([[-0.01839826,  0.04044249, -0.04871846, ...,  0.01967763,\n",
            "        -0.03034571, -0.01698984],\n",
            "       [-0.03716197,  0.0374358 ,  0.00968184, ..., -0.04857936,\n",
            "        -0.0385102 , -0.01883975],\n",
            "       [-0.01227728, -0.04690691,  0.00250578, ...,  0.01141983,\n",
            "         0.01773251,  0.03525344],\n",
            "       ...,\n",
            "       [ 0.03374172,  0.02467764,  0.00621947, ..., -0.01521915,\n",
            "        -0.01185555,  0.0295455 ],\n",
            "       [-0.04029766, -0.02826073,  0.0358924 , ..., -0.02519268,\n",
            "        -0.03909808, -0.01965014],\n",
            "       [-0.04007702, -0.04353172,  0.04063287, ...,  0.01851353,\n",
            "        -0.00767929, -0.00816654]], dtype=float32)], non_trainable=[])\n",
            "Item variables shape: (3706, 50)\n"
          ]
        }
      ],
      "source": [
        "state = training_process.initialize()\n",
        "model = training_process.get_model_weights(state)\n",
        "print(model)\n",
        "print('Item variables shape:', model.trainable[0].shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yPFqgTV21lJO"
      },
      "source": [
        "We can also try to evaluate our randomly initialized model on validation clients. Federated Reconstruction evaluation here involves the following:\n",
        "\n",
        "1. The server sends the item matrix $I$ to sampled evaluation clients\n",
        "2. Each client freezes $I$ and trains their user embedding $U_u$ using one or more steps of SGD (reconstruction)\n",
        "3. Each client calculates loss and metrics using the server $I$ and reconstructed $U_u$ on an unseen portion of their local data\n",
        "4. Losses and metrics are averaged across users to calculate overall loss and metrics\n",
        "\n",
        "Note that steps 1 and 2 are the same as for training. This connection is important, since training the same way we evaluate leads to a form of *meta-learning*, or learning how to learn. In this case, the model is learning how to learn global variables (item matrix) that lead to performant reconstruction of local variables (user embeddings). For more on this, see [Sec. 4.2](https://arxiv.org/abs/2102.03448) of the paper.\n",
        "\n",
        "It's also important for steps 2 and 3 to be performed using disjoint portions of clients' local data, to ensure fair evaluation. By default, both the training process and evaluation computation use every other example for reconstruction and use the other half post-reconstruction. This behavior can be customized using the `dataset_split_fn` argument (we'll explore this further later)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "metadata": {
        "id": "JiBOGFsWWBiU"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Initial Eval: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.365454)])\n"
          ]
        }
      ],
      "source": [
        "# We shouldn't expect good evaluation results here, since we haven't trained\n",
        "# yet!\n",
        "eval_state = evaluation_process.initialize()\n",
        "eval_state = evaluation_process.set_model_weights(\n",
        "  eval_state, training_process.get_model_weights(state)\n",
        ")\n",
        "_, eval_metrics = evaluation_process.next(eval_state, tf_val_datasets)\n",
        "print('Initial Eval:', eval_metrics['client_work']['eval'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aZUZwjWp4iJu"
      },
      "source": [
        "We can next try running a round of training. To make things more realistic, we'll sample 50 clients per round randomly without replacement. We should still expect train metrics to be poor, since we're only doing one round of training."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "metadata": {
        "id": "lOTfqrVcVfJf"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Train metrics: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.183293)])\n"
          ]
        }
      ],
      "source": [
        "federated_train_data = np.random.choice(tf_train_datasets, size=50, replace=False).tolist()\n",
        "state, metrics = training_process.next(state, federated_train_data)\n",
        "print(f'Train metrics:', metrics['client_work']['train'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Rr3ZS9jz5Mj0"
      },
      "source": [
        "Now let's set up a training loop to train over multiple rounds."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "metadata": {
        "id": "VJBzOPNYwp9q"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Train round 0: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.523704)])\n",
            "Train round 1: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.552873)])\n",
            "Train round 2: OrderedDict([('rating_accuracy', 0.0), ('loss', 14.480412)])\n",
            "Train round 3: OrderedDict([('rating_accuracy', 0.0051107327), ('loss', 12.155375)])\n",
            "Train round 4: OrderedDict([('rating_accuracy', 0.042440318), ('loss', 9.201913)])\n",
            "Train round 5: OrderedDict([('rating_accuracy', 0.11840491), ('loss', 5.5969186)])\n",
            "Train round 6: OrderedDict([('rating_accuracy', 0.12890044), ('loss', 5.5303264)])\n",
            "Train round 7: OrderedDict([('rating_accuracy', 0.19774501), ('loss', 3.9932375)])\n",
            "Train round 8: OrderedDict([('rating_accuracy', 0.21234067), ('loss', 3.5070496)])\n",
            "Train round 9: OrderedDict([('rating_accuracy', 0.21757619), ('loss', 3.5754187)])\n",
            "Train round 10: OrderedDict([('rating_accuracy', 0.24020319), ('loss', 3.0558898)])\n",
            "Train round 11: OrderedDict([('rating_accuracy', 0.2337753), ('loss', 3.1659348)])\n",
            "Train round 12: OrderedDict([('rating_accuracy', 0.2638889), ('loss', 2.413888)])\n",
            "Train round 13: OrderedDict([('rating_accuracy', 0.2622365), ('loss', 2.760038)])\n",
            "Train round 14: OrderedDict([('rating_accuracy', 0.27820238), ('loss', 2.195349)])\n",
            "Train round 15: OrderedDict([('rating_accuracy', 0.29124364), ('loss', 2.447856)])\n",
            "Train round 16: OrderedDict([('rating_accuracy', 0.30438596), ('loss', 2.096729)])\n",
            "Train round 17: OrderedDict([('rating_accuracy', 0.29557413), ('loss', 2.0750825)])\n",
            "Train round 18: OrderedDict([('rating_accuracy', 0.31832394), ('loss', 1.99085)])\n",
            "Train round 19: OrderedDict([('rating_accuracy', 0.3162333), ('loss', 2.0302613)])\n",
            "Final Eval: OrderedDict([('rating_accuracy', 0.3126193), ('loss', 2.0305126)])\n"
          ]
        }
      ],
      "source": [
        "NUM_ROUNDS = 20\n",
        "\n",
        "train_losses = []\n",
        "train_accs = []\n",
        "\n",
        "state = training_process.initialize()\n",
        "\n",
        "# This may take a couple minutes to run.\n",
        "for i in range(NUM_ROUNDS):\n",
        "  federated_train_data = np.random.choice(tf_train_datasets, size=50, replace=False).tolist()\n",
        "  state, metrics = training_process.next(state, federated_train_data)\n",
        "  print(f'Train round {i}:', metrics['client_work']['train'])\n",
        "  train_losses.append(metrics['client_work']['train']['loss'])\n",
        "  train_accs.append(metrics['client_work']['train']['rating_accuracy'])\n",
        "\n",
        "\n",
        "eval_state = evaluation_process.set_model_weights(\n",
        "  eval_state, training_process.get_model_weights(state))\n",
        "_, eval_metrics = evaluation_process.next(eval_state, tf_val_datasets)\n",
        "print('Final Eval:', eval_metrics['client_work']['eval'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yM-jAGNm5di7"
      },
      "source": [
        "We can plot training loss and accuracy over rounds. The hyperparameters in this notebook have not been carefully tuned, so feel free to try different clients per round, learning rates, number of rounds, and total number of clients to improve these results."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "metadata": {
        "id": "h6w702JmR-3V"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\nO60zGG/r7ObAkXb2H2ln35F29h2OjW/Yc4j9R9o52NJBX/vOZrEeVUvzs+no8jdCvKWjq8/lBzJ9\nVNFJB39/kh78PZnZJOBMYEUf824CbgKYMGFCcgsTkWHBzN5o3kmEzq5uDjR3BF8MbewPviT2Hm5n\n/5E2Gls6yYlkkZ+TRX52hPzsCHk5EfKiEfJzgufZb47nZ0fIz8mKTTs6LwGnrYYW/GZWCDwAfMbd\nm3rPd/c7gDsg1tST5PJERI4rGsmisiiXyqJcoCjscuIWysmvZpZNLPTvc/cHw6hBRCRTJT34LXZJ\n353AK+7+3WSvX0Qk04Wxx38BcB1wiZm9GAxXhVCHiEhGCuOsnmVA+p0QKyIyTKiDCxGRDKPgFxHJ\nMAp+EZEMo+AXEckwadE7p5k1ADtO8uUVwN4hLGeoqb7BUX2Do/oGL5VrnOjulb0npkXwD4aZreyr\nk6JUofoGR/UNjuobvHSosTc19YiIZBgFv4hIhsmE4L8j7AKOQ/UNjuobHNU3eOlQ4zGGfRu/iIgc\nKxP2+EVEpAcFv4hIhhk2wW9mV5jZBjPbbGa39jHfzOz7wfy1ZnZWEmsbb2Z/NLNXzOxlM/t0H8ss\nMLPGHj2WfjlZ9QXr325mLwXrfssNjkPefjN7bJcXzazJzD7Ta5mkbj8zu8vM6s1sXY9pZWb2lJlt\nCh5H9vPaAT+rCazvO2b2avDv95CZlfbz2gE/Cwms76tm9vrxeu0Ncfv9qkdt283sxX5em/DtN2ju\nnvYDEAG2AFOAHGANcGqvZa4CniDWM+g8YEUS66sCzgrGi4CNfdS3AHg0xG24HagYYH5o26+Pf+s9\nxC5MCW37ARcDZwHrekz7NnBrMH4r8K1+6h/ws5rA+i4HosH4t/qqL57PQgLr+yrwhTj+/UPZfr3m\n/yvw5bC232CH4bLHfw6w2d23uns78N/Au3ot8y7gZx6zHCg1s6pkFOfute7+QjB+CDh6g/l0Etr2\n62UhsMXdT/ZK7iHh7s8A+3tNfhdwbzB+L/DuPl4az2c1IfW5+5Pu3hk8XQ6MG+r1xquf7ReP0Lbf\nUcHNpD4A3D/U602W4RL8Y4HXejzfxVuDNZ5lEm6gG8wD55nZGjN7wsxOS25lOPCkma0KbnTfW0ps\nP+Cv6f8/XJjbD2C0u9dC7MseGNXHMqmyHW8k9guuL8f7LCTSJ4OmqLv6aSpLhe13EVDn7pv6mR/m\n9ovLcAn+vm7s0vs81XiWSSgb+AbzLxBrvpgL/AD4bTJrAy5w97OAK4FPmNnFveanwvbLAa4Bft3H\n7LC3X7xSYTveBnQC9/WzyPE+C4nyI2AqcAZQS6w5pbfQtx/wQQbe2w9r+8VtuAT/LmB8j+fjgN0n\nsUzC2HFuMO/uTe5+OBh/HMg2s4pk1efuu4PHeuAhYj+pewp1+wWuBF5w97reM8LefoG6o81fwWN9\nH8uE/Tm8Hrga+LAHDdK9xfFZSAh3r3P3LnfvBn7Sz3rD3n5R4L3Ar/pbJqztdyKGS/D/BZhuZpOD\nvcK/Bh7ptcwjwN8EZ6fMAxqP/ixPtKBNcMAbzJvZmGA5zOwcYv82+5JU3wgzKzo6Tuwg4Lpei4W2\n/Xrod08rzO3XwyPA9cH49cDDfSwTz2c1IczsCuAW4Bp3b+5nmXg+C4mqr+cxo/f0s97Qtl/gUuBV\nd9/V18wwt98JCfvo8lANxM462UjsiP9twbS/B/4+GDfgP4L5LwE1SaztQmI/R9cCLwbDVb3q+yTw\nMrGzFJYD5yexvinBetcENaTU9gvWX0AsyEt6TAtt+xH7AqoFOojthX4MKAf+AGwKHsuCZauBxwf6\nrCapvs3E2sePfgZ/3Lu+/j4LSarv58Fnay2xMK9Kpe0XTL/n6Geux7JJ336DHdRlg4hIhhkuTT0i\nIhInBb+ISIZR8IuIZBgFv4hIhlHwi4hkGAW/CGBmXUFviuvMbHF/PVcO4fo+amY/TOQ6RPqj4BeJ\naXH3M9x9NrHOuT4RdkEiiaLgF3mr5wk6/jKzM8xseY8+7EcG05eYWU0wXmFm24Pxj5rZg2b2O4v1\ny//to29qZjeY2UYzWwpckPS/SiSg4BfpwcwixLp+PtoNwM+AW9x9DrGrSr8Sx9ucAfwVcDrwVxa7\nEU8V8DVigX8ZcOoQly4SNwW/SEx+cEelfUAZ8JSZlQCl7r40WOZeYjfoOJ4/uHuju7cC64GJwLnA\nEndv8Fg/8v128iWSaAp+kZgWdz+DWEjncPw2/k7e/P+T12teW4/xLiAajKt/FEkJCn6RHty9EfgU\n8AWgGThgZhcFs68Dju79bwfODsavjeOtVwALzKw86KL7/UNWtMgJih5/EZHM4u6rzWwNsS5/rwd+\nbGYFwFbghmCx24H/MbPrgKfjeM9aM/sqsQPHtcRuHBNJQPkix6XeOUVEMoyaekREMoyCX0Qkwyj4\nRUQyjIJfRCTDKPhFRDKMgl9EJMMo+EVEMsz/B+huDHc/R5R0AAAAAElFTkSuQmCC\n",
            "text/plain": [
              "\u003cFigure size 600x400 with 1 Axes\u003e"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "\u003cFigure size 600x400 with 1 Axes\u003e"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "plt.plot(range(NUM_ROUNDS), train_losses)\n",
        "plt.ylabel('Train Loss')\n",
        "plt.xlabel('Round')\n",
        "plt.title('Train Loss')\n",
        "plt.show()\n",
        "\n",
        "plt.plot(range(NUM_ROUNDS), train_accs)\n",
        "plt.ylabel('Train Accuracy')\n",
        "plt.xlabel('Round')\n",
        "plt.title('Train Accuracy')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZTzKkT-a5kgX"
      },
      "source": [
        "Finally, we can calculate metrics on an unseen test set when we're finished tuning."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "metadata": {
        "id": "Iq0UxEBBJcR-"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Final Test: OrderedDict([('rating_accuracy', 0.3129535), ('loss', 1.9429641)])\n"
          ]
        }
      ],
      "source": [
        "eval_state = evaluation_process.set_model_weights(\n",
        "    eval_state, training_process.get_model_weights(state)\n",
        ")\n",
        "_, eval_metrics = evaluation_process.next(eval_state, tf_test_datasets)\n",
        "print('Final Test:', eval_metrics['client_work']['eval'])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Mr2fRxic6Lfi"
      },
      "source": [
        "## Further Explorations\n",
        "\n",
        "Nice work on completing this notebook. We suggest the following exercises to explore partially local federated learning further, roughly ordered by increasing difficulty:\n",
        "\n",
        "* Typical implementations of Federated Averaging take multiple local passes (epochs) over the data (in addition to taking one pass over the data across multiple batches). For Federated Reconstruction we may want to control the number of steps separately for reconstruction and post-reconstruction training. Passing the `dataset_split_fn` argument to the training and evaluation computation builders enables control of the number of steps and epochs over both reconstruction and post-reconstruction datasets. As an exercise, try performing 3 local epochs of reconstruction training, capped at 50 steps and 1 local epoch of post-reconstruction training, capped at 50 steps. Hint: you'll find [`tff.learning.models.ReconstructionModel.build_dataset_split_fn`](https://www.tensorflow.org/federated/api_docs/python/tff/learning/models/ReconstructionModel#build_dataset_split_fn) helpful. Once you've done this, try tuning these hyperparameters and other related ones like learning rates and batch size to get better results.\n",
        "\n",
        "* The default behavior of Federated Reconstruction training and evaluation is to split clients' local data in half for each of reconstruction and post-reconstruction. In cases where clients have very little local data, it can be reasonable to reuse data for reconstruction and post-reconstruction for the training process only (not for evaluation, this will lead to unfair evaluation). Try making this change for the training process, ensuring the `dataset_split_fn` for evaluation still keeps reconstruction and post-reconstruction data disjoint. Hint: [`tff.learning.models.ReconstructionModel.simple_dataset_split_fn`](https://www.tensorflow.org/federated/api_docs/python/tff/learning/models/ReconstructionModel#simple_dataset_split_fn) might be useful.\n",
        "\n",
        "* Above, we produced a `tff.learning.models.VariableModel` from a Keras model using `tff.learning.models.ReconstructionModel.from_keras_model_and_layers`. We can also implement a custom model using pure TensorFlow 2.0 by [implementing the model interface](https://www.tensorflow.org/federated/api_docs/python/tff/learning/models/ReconstructionModel). Try modifying `get_matrix_factorization_model` to build and return a class that extends `tff.learning.models.ReconstructionModel`, implementing its methods. Hint: the source code of [`tff.learning.models.ReconstructionModel.from_keras_model_and_layers`](https://www.tensorflow.org/federated/api_docs/python/tff/learning/models/ReconstructionModel#from_keras_model_and_layers) provides an example of extending the `tff.learning.models.ReconstructionModel` class. Refer also to the [custom model implementation in the EMNIST image classification tutorial](https://www.tensorflow.org/federated/tutorials/federated_learning_for_image_classification#customizing_the_model_implementation) for a similar exercise in extending a `tff.learning.models.VariableModel`.\n",
        "\n",
        "* In this tutorial, we've motivated partially local federated learning in the context of matrix factorization, where sending user embeddings to the server would trivially leak user preferences. We can also apply Federated Reconstruction in other settings as a way to train more personal models (since part of the model is completely local to each user) while reducing communication (since local parameters are not sent to the server). In general, using the interface presented here we can take any federated model that would typically be trained fully globally and instead partition its variables into global variables and local variables. The example explored in the [Federated Reconstruction paper](https://arxiv.org/abs/2102.03448) is personal next word prediction: here, each user has their own local set of word embeddings for out-of-vocabulary words, enabling the model to capture users' slang and achieve personalization without additional communication. As an exercise, try implementing (as either a Keras model or a custom TensorFlow 2.0 model) a different model for use with Federated Reconstruction. A suggestion: implement an EMNIST classification model with a personal user embedding, where the personal user embedding is concatenated to the CNN image features before the last Dense layer of the model. You can reuse much of the code from this tutorial (e.g. the `UserEmbedding` class) and the [image classification tutorial](https://www.tensorflow.org/federated/tutorials/federated_learning_for_image_classification).\n",
        "\n",
        "\\\n",
        "If you're still looking for more on partially local federated learning, check out the [Federated Reconstruction paper](https://arxiv.org/abs/2102.03448) and [open-source experiment code](https://github.com/google-research/federated/tree/master/reconstruction)."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "name": "federated_reconstruction_for_matrix_factorization.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
